package com.android.webserver.security;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import com.android.webserver.db.WebApplication;
import com.android.webserver.tornado.Cookie;
import com.android.webserver.tornado.HTTP;
import com.android.webserver.tornado.Request;
import com.android.webserver.tornado.Response;
import com.android.webserver.tornado.Tornado;
import com.android.webserver.util.Base64;

/**
 * @author Baptiste Gourdin
 *
 */
public class Session
{
	private HashMap<String, Long>		sessions;
	private HashMap<String, String>	redirs;

	private static final long				SESSION_EXPIRATION	= 600000;				// ms => 1
	// min
	private static final String			SESSION_COOKIE			= "session-id";
	

	public Session(Context context)
	{
		sessions = new HashMap<String, Long>();
		redirs = new HashMap<String, String>();
	}


	/**
	 * Generate a secureId string using SecureRandom
	 * From http://www.javapractices.com/topic/TopicAction.do?Id=56
	 * @return The generated random string.
	 */
	public static String generateId()
	{
		try
		{
			// Initialize SecureRandom
			// This is a lengthy operation, to be done only upon
			// initialization of the application
			SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");

			// generate a random number
			String randomNum = new Integer(prng.nextInt()).toString();

			// get its digest
			MessageDigest sha = MessageDigest.getInstance("SHA-1");
			byte[] result = sha.digest(randomNum.getBytes());

			return (Base64.encodeBytes(result));
		}
		catch (NoSuchAlgorithmException ex)
		{
			Log.e("WebServer", "Session:generateId NoSuchAlgorithmException");
			return null;
		}
	}

	
	/**
	 * Get the session cookie from a request
	 * @param request The HTTP request
	 * @return The session cookie is returned if present, null else.
	 */
	public static String getSessionId(Request request)
	{
		return request.getCookie(SESSION_COOKIE);
	}

	/**
	 * Handle for an authentication request (path : /path)
	 * @param webApp	WebApplication to authenticate
	 * @param request Authentication request
	 * @param response Authentication response
	 * @return True if the authentication succeeded, false else.
	 */
	public void verifyPasswordAndCreateSession(Request request, Response response)
	{
		// Authentication request MUST be done by POST
		if (!request.getType().equals("POST"))
			return ;
		
		// Retreive the application using its is in POST[appId]
		WebApplication webApp = Tornado.androidService.db
				.getWebApplicationFromId(Long.parseLong(request.getParameter("appId")));

		// Base64 encoded Sha1 (password)
		String sha1Pass = request.getParameter("password");
		if ((sha1Pass != null) && (sha1Pass.equals(webApp.getPassword())))
		{
			// Generate a session id and add it to the current sessions
			String sessionId = generateId();
			sessions.put(webApp.getId() + "-" + sessionId, System.currentTimeMillis()
					+ SESSION_EXPIRATION);
			try
			{
				// Create the session cookie 
				Cookie c = new Cookie(new URL(request.getURI()), SESSION_COOKIE,
						sessionId);
				c.setPath("/" + webApp.getPath());
				
				String redir = redirs.get(request.getParameter("redir"));
				if (redir == null)
					redir = webApp.getPath();
				response.addHeader("Set-Cookie: " + c.toString());
				response.addHeader("Location: " + redir);
				response.setHTTPStatusCode(HTTP.FOUND);
			}
			catch (MalformedURLException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Look for a session token in the request. If not present or expired, generate an authentication page.
	 * @param webApp	WebApplication 
	 * @param request	HTTP Request
	 * @param response	HTTP response
	 * @return	True if authenticated, false else.
	 */
	public boolean isAuthentified(WebApplication webApp, Request request,
			Response response)
	{
		if ((webApp.getPassword() == null) || (webApp.getPassword().equals(""))) // No password required
			return true;

		String sessionId = getSessionId(request);
		
		if (sessionId == null)
		{
			createAuthenticationPage(webApp, request, response);
			return false;
		}
		String key = webApp.getId() + "-" + sessionId;
		Long expire = sessions.get(key);
		Long now = System.currentTimeMillis();
		if (expire == null)
		{
			createAuthenticationPage(webApp, request, response);
			return false;
		}

		if (expire > now)
			return true;
		else
		{
			sessions.remove(key);
			createAuthenticationPage(webApp, request, response);
			return false;
		}
	}

	/**
	 * Create an authentication page for a specific webApp
	 * @param webApp	WebApplication to authenticate
	 * @param request	HTTP request used to redirect to this request path after authentication.
	 * @param response HTTP Response
	 */
	private void createAuthenticationPage(WebApplication webApp, Request request,
			Response response)
	{
		String redir = generateId();
		redirs.put(redir, request.getURI());

		response.setHTTPStatusCode(HTTP.OK);
		response.HTMLContent = "<html><head>" + "<title>" + TextUtils.htmlEncode(webApp.getName())
				+ " authentification page</title>"
				+ "<script type=\"text/javascript\" src=\"/sha1.js\"></script>"
				+ "<script type=\"text/javascript\" src=\"/authentify.js\"></script>"
				+ "</head><body>" + "<center>" + "<h2>" + TextUtils.htmlEncode(webApp.getName())
				+ " password </h2>" + "<form method=\"POST\" action=\"/auth\">"
				+ "<input type=\"password\" name=\"password\" id=\"password\"/>"
				+ "<input type=\"hidden\" name=\"appId\" value=\"" + webApp.getId()
				+ "\" />" + "<input type=\"hidden\" name=\"redir\" value=\""
				+ redir + "\" />"
				+ "<input type=\"button\" id=\"sendButton\" value=\"send\"/>"
				+ "</form>" + "</center></body></html>";
		response.addHeader("Content-Length: " + response.HTMLContent.length());
		response.addHeader("Content-Type: text/html");
	}
}
